/*
    这一份代码是先简单的使用一下websocket协议的
    我们后面实现具体的项目的时候，其实编写的就是下面的四个具体的函数
*/


/*
    这个文件是配合当前目录下的 index.html 使用的。
    当前目录下的简单 index.html 前端页面作为client，
    可以下载到桌面用浏览器(注意得用配套的浏览器打开的哦<meta http-equiv="X-UA-Compatible" content="IE=edge">)。
    打开后就是一个 index.html 和 ./wsserver 通信了
*/


#include <iostream>
#include <string>
#include <cstring>
#include <websocketpp/server.hpp>
#include <websocketpp/config/asio_no_tls.hpp>

using namespace std;

// 简化类型长度
typedef websocketpp::server<websocketpp::config::asio> wsserver_t;

// 简单的一个打印函数
void print(const std::string &str){
    std::cout << str << std::endl;
}

// void http_callback(websocketpp::connection_hdl hdl){}
// void wsopen_callback(websocketpp::connection_hdl hdl){}
// void wsclose_callback(websocketpp::connection_hdl hdl){}
// void wsmsg_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg){}

// 使用上面的代码也是可以的
// 但是如果要函数要使用 wsserver_t *srv 的话，上面的函数时不行的。
// 所以为了扩展性，我们使用了 c++11 里面的bind绑定一下后面需要传递的参数，前面的 wsserver_t *srv 是我们自己加上去的，为了函数可能要使用 wsserver_t *srv 而设置的。
// 自己已经固定要传递的参数读写在前面的位置上，后面是预留要传递的参数的哦。

// wobsocket协议最开始是tcp的三次握手建立连接，然后是http请求变成websocket协议通信，最后就是用的是websocket协议来通信了。

// 测试：收到 http 请求会调用的函数。
void http_callback(wsserver_t *srv, websocketpp::connection_hdl hdl){
    //给客户端返回一个hello world的页面
    // 通信连接的对象
    wsserver_t::connection_ptr conn = srv->get_con_from_hdl(hdl);
    std::cout << "body: " << conn->get_request_body() << std::endl; 
    websocketpp::http::parser::request req = conn->get_request();
    std::cout << "method: " << req.get_method() << std::endl;
    std::cout << "uri: " << req.get_uri() << std::endl;

    std::string body = "<html><body><h1>Hello World</h1></body></html>";
    // 使用下面这一行的时候，是测试最开始时的 http:://43.143.88.137:8080 
    // conn->set_body(body);                    // 用这一行代码的话页面返回的是一个 hello world 的哦 --> debug 用的
    
    conn->set_body(conn->get_request_body());   // 用这一行代码才是正确的
    conn->append_header("Content-Type", "text/html");
    conn->set_status(websocketpp::http::status_code::ok);
    wsserver_t::timer_ptr tp = srv->set_timer(5000, std::bind(print, "hello world"));
    tp->cancel();//定时任务的取消，会导致定时任务立即被执行
}

// 下面三个测试得配合 index.html 页面，因为里面有相关的输入窗口发送信息
// 测试：建立请求会调用的函数
void wsopen_callback(wsserver_t *srv, websocketpp::connection_hdl hdl){
    std::cout << "websocket握手成功!!!\n" << std::endl;
}
// 测试：断开请求会调用的函数
void wsclose_callback(wsserver_t *srv, websocketpp::connection_hdl hdl){
    std::cout << "websocket连接断开!!!\n" << std::endl;
}
// 注意 wsmsg_callback 是有两个参数的：
// #include <websocketpp/server.hpp>
// #include <websocketpp/config/asio_no_tls.hpp>
// 上面的两个头文件里面的 namespace websocketpp 可以看出来，我们也有看到里面的常用的接口
// 测试：浏览器的页面作为client向wensocketServer发送信息会调用的函数
void wsmsg_callback(wsserver_t *srv, websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg){
    // 通信连接的对象
    wsserver_t::connection_ptr conn = srv->get_con_from_hdl(hdl);
    
    std::cout << "wsmsg: " << msg->get_payload() << std::endl;
    std::string rsp = "client say: " + msg->get_payload();
    conn->send(rsp, websocketpp::frame::opcode::text);
}


int main(void){
    // 在开始写代码之前得清楚知道自己要写的是什么
    // 1. 实例化server对象
    // 2. 设置日志等级
    // 3. 初始化asio调度器
    // 4. 设置回调函数
    // 5. 设置监听端口
    // 6. 开始获取新连接
    // 7. 启动服务器

    // 1. 实例化server对象
    // websocketpp::server<websocketpp::config::asio> wsserver;
    // 因为上面的类型过长了，所以在上面的ypedef一下
    wsserver_t wssrv;
    // 2. 设置日志等级
    // 这里在我们先不设置日志了
    wssrv.set_access_channels(websocketpp::log::alevel::none);
    // 3. 初始化asio调度器
    wssrv.init_asio();
    wssrv.set_reuse_addr(true);  // 设置端口复用
    // 4. 设置回调函数
    // wssrv.set_http_handler(http_callback);
    // wssrv.set_open_handler(wsopen_callback);
    // wssrv.set_close_handler(wsclose_callback);
    // wssrv.set_message_handler(wsmsg_callback);
    
    // 上面的代码也是可以的
    wssrv.set_http_handler(std::bind(http_callback, &wssrv, std::placeholders::_1));
    wssrv.set_open_handler(std::bind(wsopen_callback, &wssrv, std::placeholders::_1));
    wssrv.set_close_handler(std::bind(wsclose_callback, &wssrv, std::placeholders::_1));
    wssrv.set_message_handler(std::bind(wsmsg_callback, &wssrv, std::placeholders::_1, std::placeholders::_2));
    // 5. 设置监听端口
    wssrv.listen(8080);   // 8085 .... 也是可以的
    // 6. 开始获取新连接
    wssrv.start_accept();
    // 7. 启动服务器
    wssrv.run();

    return 0;
}

